class Compiler:

    def __init__(self):
        self.__init_symbols()
        self.__init_translate()

    def process(self, code):
        code = self.first_pass(code)
        data = []
        for line in self.second_pass(code):
            print(line, '\n====>', end='')
            out = self.translate(line)
            print(out)
            data.append(out)
        self.write(data)

    def first_pass(self, code):
        for line_num, line_code in code.items():
            right_bracket = line_code.find(")")
            if right_bracket != -1:
                self.symbols[line_code[1:right_bracket]] = line_num
                line_code = line_code[right_bracket + 1 :]
            if line_code.startswith("@"):  # A instruction
                a_instru = line_code[1:]
                if a_instru.isdigit():  # number
                    continue
                elif self.symbols.get(a_instru, None) is not None:  # defined symbol
                    code[line_num] = f"@{self.symbols[a_instru]}"
                elif self.variables.get(a_instru, None) is not None:  # varibles
                    code[line_num] = "@%s" % self.symbols[self.variables[a_instru]]
                else:  # undefined symbol
                    self.variables[a_instru] = f"R{15-self.current_R_idx}"
                    code[line_num] = "@%s" % self.symbols[f"R{15-self.current_R_idx}"]
                    self.current_R_idx += 1
        return code

    def second_pass(self, code):
        for line_num, line_code in code.items():
            if line_code.startswith("@"):
                yield "A_COMMAND", line_code[1:]
            else:  # 'C_COMMAND , with dest=comp;jump'
                eq_idx = line_code.find("=")
                sem_idx = line_code.find(";")
                if sem_idx == -1:  # ommit ; dest=comp type
                    dest = line_code[:eq_idx]
                    comp = line_code[eq_idx + 1 :]
                    yield "C_COMMAND", (dest, comp, "null")
                elif eq_idx == -1:  # ommit = comp;jump type
                    comp = line_code[:sem_idx]
                    jmp = line_code[sem_idx + 1 :]
                    yield "C_COMMAND", ("null", comp, jmp)
                else:
                    dest = line_code[:eq_idx]
                    comp = line_code[eq_idx + 1 : sem_idx]
                    jmp = line_code[sem_idx + 1 :]
                    yield "C_COMMAND", (dest, comp, jmp)

    def write(self, data):
        with open("out.hack", "wb") as f:
            for line in data:
                if line:
                    number = int(line, 2)  # 将二进制字符串转换为整数
                    f.write(number.to_bytes(2, byteorder="big"))

    def handle_A(self, code):
        return "0" + bin(int(code))[2:].zfill(15)

    def handle_C(self, code):
        dest, comp, jmp = code
        return (
            "111" + self.comp_table[comp] + self.dest_table[dest] + self.jmp_table[jmp]
        )

    def translate(self, line_code):
        command_type, command = line_code
        if command_type == "A_COMMAND":
            return self.handle_A(command)
        elif command_type == "C_COMMAND":
            return self.handle_C(command)
        else:
            ...

    def __init_translate(self):
        self.dest_table = {
            "null": "000",
            "M": "001",
            "D": "010",
            "MD": "011",
            "A": "100",
            "AM": "101",
            "AD": "110",
            "AMD": "111",
        }

        self.jmp_table = {
            "null": "000",
            "JGT": "001",
            "JEQ": "010",
            "JGE": "011",
            "JLT": "100",
            "JNE": "101",
            "JLE": "110",
            "JMP": "111",
        }

        self.comp_table = {
            "0": "0101010",
            "1": "0111111",
            "-1": "0111010",
            "D": "0001100",
            "A": "0110000",
            "!D": "0001101",
            "!A": "0110001",
            "-D": "0001111",
            "-A": "0110011",
            "D+1": "0011111",
            "A+1": "0110111",
            "D-1": "0001110",
            "A-1": "0110010",
            "D+A": "0000010",
            "D-A": "0010011",
            "A-D": "0000111",
            "D&A": "0000000",
            "D|A": "0010101",
            "M": "1110000",
            "!M": "1110001",
            "-M": "1110011",
            "M+1": "1110111",
            "M-1": "1110010",
            "D+M": "1000010",
            "D-M": "1010011",
            "M-D": "1000111",
            "D&M": "1000000",
            "D|M": "1010101",
        }

    def __init_symbols(self):
        self.symbols = {}
        self.variables = {}
        self.labels = {}
        self.current_R_idx = 0
        # Virtual registers The virtual registers symbols R0,…, R15 .are automatically predefined to refer to RAM addresses 0,…,15
        for idx in range(16):
            self.symbols[f"R{idx}"] = idx

        #  • I/O pointers The symbols SCREEN and KBD are automatically  predefined to refer to RAM addresses 16384 24576, respectively
        self.symbols["SCREEN"] = 16384
        self.symbols["KBD"] = 24576

        # • VM control pointers The symbols SP, LCL, ARG, THIS, and THAT are  predefined to refer to RAM addresses 0 to 4. (will be  covered later in this semester)
        self.symbols["SP"] = 0
        self.symbols["LCL"] = 1
        self.symbols["ARG"] = 2
        self.symbols["THIS"] = 3
        self.symbols["THAT"] = 4
